今天我們要來把剩下查詢相關的API開發完成,今天要開發的API如下:
我們可以把tag和title都當作查詢的參數去設計這個API。
這樣的好處是可以更靈活地增加查詢條件,當有新的查詢條件時不需要再去新增一個路由。
因為我們之前已經建立過posts-controller.js
,就直接在裡面新增這次的API邏輯。
//post-controller.js
const HttpError = require('../models/http-error');
const Post = require('../models/Post');
...(略)
//取得所有文章
exports.getAllPost = async (req,res,next) =>{
try {
const { tag, title } = req.query;
let queryObj = {};
//若有tag參數
if (tag) {
queryObj.tags = { $regex: new RegExp(tag, 'i') }; // 使用正規表達式進行模糊搜尋
}
//若有title參數
if (title) {
queryObj.title = new RegExp(title, 'i'); // 使用正規表達式進行模糊搜尋
}
const posts = await Post.find(queryObj);
res.json(posts);
} catch (err) {
next(new HttpError('Server error', 500));
}
}
接著到posts-routes.js
新增取得所有文章API的路徑,並記得加上昨天開發的auth middleware
//posts-routes.js
const express = require("express");
const router = express.Router();
const postControllers = require("../../controllers/posts-controller");
const auth = require("../../middleware/auth");
...(略)
//@router GET api/posts
//@desc 取得所有文章
//@access Public
router.get("/", auth, postControllers.getAllPost);
module.exports = router;
雖然可以透過上一支API來取得文章列表後再到前端透過tag來進行分組,但這裡想嘗試看看在後端進行處理後再回傳。
我們希望可以得到的回傳格式如下
[
{
"tag": "frontend",
"posts" : [
{
"title":"文章1",
"content":"內容1",
"tags": ['frontend','fullstack'],
"createdDate": "2023-09-24T09:31:47.123Z",
"authorId":"65142d3f92e81e096e3d7752"
},
{
"title":"文章2",
"content":"內容2",
"tags": ['frontend'],
"createdDate": "2023-09-22T10:21:55.123Z",
"authorId":"691f3d3f92e81e096e3d7752"
},
{
"title":"文章3",
"content":"內容3",
"tags": ['frontend','node'],
"createdDate": "2023-09-22T02:41:10.123Z",
"authorId":"761f3d3f92e81e096e3d7752"
}
]
},
{
"tag": "fullstack",
"posts" : [
{
"title":"文章1",
"content":"內容1",
"tags": ['frontend','fullstack'],
"createdDate": "2023-09-24T09:31:47.123Z",
"authorId":"961f3d3f92e81e096e3d7752"
}
]
},
]
一樣在post-controller.js
新增API handle function
//post-controller.js
const HttpError = require('../models/http-error');
const Post = require('../models/Post');
...(略)
//取得所有文章(依照標籤分類)
exports.getPostsByTag = async (req,res,next) =>{
try {
// 找出所有的標籤
const distinctTags = await Post.distinct('tags');
// 對於每個標籤,找出含有該標籤的文章
const results = [];
for (let tag of distinctTags) {
const postsWithTag = await Post.find({ tags: tag }).sort({ createdDate: -1 }).select('title content tags createdDate');
results.push({
tag,
posts: postsWithTag
});
}
res.json(results);
} catch (err) {
next(new HttpError('Server error', 500));
}
}
以上做法看似很直觀沒有任何錯誤,但這樣其實隱含著效能問題,每當我們迴圈一次就要查詢一次資料庫,若有10個不一樣的tag,就要查詢10次。
此時就可以使用MongoDB中的Aggregation Pipeline
來優化這樣的情境。
它允許開發者在MongoDB伺服器上對資料集進行一系列的操作和轉換。
基本概念:
常見的聚合階段:
這裡只簡單介紹,詳細資料請見官網
//取得所有文章(依照標籤分類)
exports.getPostsByTag = async (req,res,next) =>{
try {
const results = await Post.aggregate([
// 使用 $unwind 來擴展 tags這個陣列
{
$unwind: "$tags"
},
//根據tag來組合回傳的資料
{
$group: {
_id: "$tags", // 使用 tag 作為群組的 ID
posts: {
$push: {
title: "$title",
content: "$content",
tags: "$tags",
authorId: "$authorId",
createdDate: "$createdDate",
}
}
}
},
//根據文章建立時間排序
{
$sort: { "posts.createdDate": -1 }
},
// 調整回傳資料
{
$project: {
tag: "$_id",
posts: 1, //保留posts。數字1代表該欄位被包含在回傳資料中。
_id: 0 // 將_id從回傳內容中排除。因為每個MongoDB文件都會有一個自動生成的_id,但在這裡我們不希望它出現在回傳的資料中
}
}
]);
res.json(results);
} catch (err) {
next(new HttpError('Server error', 500));
}
}
調整後只需要呼叫一次資料庫,大幅優化效能。
接著到posts-routes.js
新增API的路徑
//posts-routes.js
const express = require("express");
const router = express.Router();
const postControllers = require("../../controllers/posts-controller");
const auth = require("../../middleware/auth");
...(略)
//@router GET api/posts/byTag
//@desc 取得所有文章(依照標籤分類)
//@access Public
router.get("/byTag", auth, postControllers.getPostsByTag);
module.exports = router;
在users-controller.js
新增API handle function
//users-controller.js
const HttpError = require('../models/http-error');
const User = require('../models/User');
const Post = require('../models/Post'); //記得要引入post model
//取得該使用者發布的文章
exports.getUserPosts = async (req, res) => {
try {
//先判斷該User是否存在
const userId = req.params.userId;
const user = await User.findById(userId);
if (!user) {
return next(new HttpError('找不到該使用者', 404));
}
const posts = await Post.find({ authorId: userId });
res.json(posts);
} catch (err) {
next(new HttpError('Server error', 500));
}
};
到users-routes.js
新增route,並記得加上auth middleware
//users-routes.js
const express = require('express');
const router = express.Router();
const userControllers = require('../../controllers/users-controller');
const auth = require("../../middleware/auth");
//@router GET api/users/:userId/posts
//@desc 取得使用者發佈的文章
//@access Public
router.get('/:userId/posts',auth,userControllers.getUserPosts);